<template>
  <Popover
    transition="default"
    placement="left"
    class="!block w-full"
    popoverClass="!min-w-fit !mr-[30px]"
  >
    <template #target="{ togglePopover, isOpen }">
      <slot
        name="target"
        :togglePopover="
          () => {
            togglePopover()
            setSelectorPosition(modelColor)
          }
        "
        :isOpen="isOpen"
      ></slot>
    </template>
    <template #body>
      <div
        ref="colorPicker"
        class="rounded-lg bg-white p-3 shadow-lg dark:bg-zinc-900"
      >
        <div
          ref="colorMap"
          :style="{
            background: `
							linear-gradient(0deg, black, transparent),
							linear-gradient(90deg, white, transparent),
							hsl(${hue}, 100%, 50%)
						`,
          }"
          @mousedown.stop="handleSelectorMove"
          class="relative m-auto h-24 w-44 rounded-md"
          @click.prevent="setColor"
        >
          <div
            ref="colorSelector"
            @mousedown.stop="handleSelectorMove"
            class="absolute rounded-full border border-black border-opacity-20 before:absolute before:h-full before:w-full before:rounded-full before:border-2 before:border-white before:bg-[currentColor] after:absolute after:left-[2px] after:top-[2px] after:h-[calc(100%-4px)] after:w-[calc(100%-4px)] after:rounded-full after:border after:border-black after:border-opacity-20 after:bg-transparent"
            :style="{
							height: '12px',
							width: '12px',
							left: `calc(${colorSelectorPosition.x}px - 6px)`,
							top: `calc(${colorSelectorPosition.y}px - 6px)`,
							color: modelColor,
							background: 'transparent',
						} as StyleValue"
          ></div>
        </div>
        <div
          ref="hueMap"
          class="relative m-auto mt-2 h-3 w-44 rounded-md"
          @click="setHue"
          @mousedown="handleHueSelectorMove"
          :style="{
            background: `
							linear-gradient(90deg, hsl(0, 100%, 50%),
							hsl(60, 100%, 50%), hsl(120, 100%, 50%),
							hsl(180, 100%, 50%), hsl(240, 100%, 50%),
							hsl(300, 100%, 50%), hsl(360, 100%, 50%))
						`,
          }"
        >
          <div
            ref="hueSelector"
            @mousedown="handleHueSelectorMove"
            class="absolute rounded-full border border-[rgba(0,0,0,.2)] before:absolute before:h-full before:w-full before:rounded-full before:border-2 before:border-white before:bg-[currentColor] after:absolute after:left-[2px] after:top-[2px] after:h-[calc(100%-4px)] after:w-[calc(100%-4px)] after:rounded-full after:border after:border-[rgba(0,0,0,.2)] after:bg-transparent"
            :style="{
              height: '12px',
              width: '12px',
              left: `calc(${hueSelectorPosition.x}px - 6px)`,
              color: `hsl(${hue}, 100%, 50%)`,
              background: 'transparent',
            }"
          ></div>
        </div>
        <div ref="colorPalette">
          <div class="mt-3 flex flex-wrap gap-1.5">
            <div
              v-for="color in colors"
              :key="color"
              class="h-3.5 w-3.5 cursor-pointer rounded-full shadow-sm"
              @click="
                () => {
                  setSelectorPosition(color)
                  updateColor()
                }
              "
              :style="{
                background: color,
              }"
            ></div>
            <svg
              v-if="isSupported"
              class="text-gray-700 dark:text-zinc-300"
              @click="() => open()"
              xmlns="http://www.w3.org/2000/svg"
              width="16"
              height="16"
              viewBox="0 0 24 24"
            >
              <g fill="none" fill-rule="evenodd">
                <path
                  d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z"
                />
                <path
                  fill="currentColor"
                  d="M20.477 3.511a3 3 0 0 0-4.243 0l-1.533 1.533a2.991 2.991 0 0 0-3.41.581l-.713.714a2 2 0 0 0 0 2.829l-6.486 6.485a3 3 0 0 0-.878 2.122v1.8a1.2 1.2 0 0 0 1.2 1.2h1.8a3 3 0 0 0 2.12-.88l6.486-6.484a2 2 0 0 0 2.829 0l.714-.715a2.991 2.991 0 0 0 .581-3.41l1.533-1.532a3 3 0 0 0 0-4.243ZM5.507 17.067l6.485-6.485l1.414 1.414l-6.485 6.486a1 1 0 0 1-.707.293h-1v-1a1 1 0 0 1 .293-.707Z"
                />
              </g>
            </svg>
          </div>
        </div>
      </div>
    </template>
  </Popover>
</template>
<script setup lang="ts">
import { HSVToHex, HexToHSV, RGBToHex, getRGB } from "@/utils/helpers"
import { clamp, useEyeDropper } from "@vueuse/core"
import { Popover } from "frappe-ui"

import { PropType, Ref, StyleValue, computed, nextTick, ref, watch } from "vue"

const hueMap = ref(null) as unknown as Ref<HTMLDivElement>
const colorMap = ref(null) as unknown as Ref<HTMLDivElement>
const hueSelector = ref(null) as unknown as Ref<HTMLDivElement>
const colorSelector = ref(null) as unknown as Ref<HTMLDivElement>

const colorSelectorPosition = ref({ x: 0, y: 0 })
const hueSelectorPosition = ref({ x: 0, y: 0 })
let currentColor = "#FFF" as HashString

const { isSupported, sRGBHex, open } = useEyeDropper()

const props = defineProps({
  modelValue: {
    type: String as PropType<HashString | RGBString | null>,
    default: null,
  },
})

const modelColor = computed(() => {
  return getRGB(props.modelValue)
})

const emit = defineEmits(["update:modelValue"])

const colors = [
  "#FFB3E6",
  "#00B3E6",
  "#E6B333",
  "#3366E6",
  "#999966",
  "#99FF99",
  "#B34D4D",
  "#80B300",
] as HashString[]

if (!isSupported.value) {
  colors.push("#B34D4D")
}

const setColorSelectorPosition = (color: HashString) => {
  const { width, height } = colorMap.value.getBoundingClientRect()
  const { s, v } = HexToHSV(color)
  let x = clamp(s * width, 0, width)
  let y = clamp((1 - v) * height, 0, height)
  colorSelectorPosition.value = { x, y }
}

const setHueSelectorPosition = (color: HashString) => {
  const { width } = hueMap.value.getBoundingClientRect()
  const { h } = HexToHSV(color)
  const left = (h / 360) * width
  hueSelectorPosition.value = { x: left, y: 0 }
}

const handleSelectorMove = (ev: MouseEvent) => {
  setColor(ev)
  const mouseMove = (mouseMoveEvent: MouseEvent) => {
    mouseMoveEvent.preventDefault()
    setColor(mouseMoveEvent)
  }
  document.addEventListener("mousemove", mouseMove)
  document.addEventListener(
    "mouseup",
    (mouseUpEvent) => {
      document.removeEventListener("mousemove", mouseMove)
      mouseUpEvent.preventDefault()
    },
    { once: true }
  )
}

const handleHueSelectorMove = (ev: MouseEvent) => {
  setHue(ev)
  const mouseMove = (mouseMoveEvent: MouseEvent) => {
    mouseMoveEvent.preventDefault()
    setHue(mouseMoveEvent)
  }
  document.addEventListener("mousemove", mouseMove)
  document.addEventListener(
    "mouseup",
    (mouseUpEvent) => {
      document.removeEventListener("mousemove", mouseMove)
      mouseUpEvent.preventDefault()
    },
    { once: true }
  )
}

function setColor(ev: MouseEvent) {
  const clickPointX = ev.clientX
  const clickPointY = ev.clientY
  const colorMapBounds = colorMap.value.getBoundingClientRect()

  let pointX = clickPointX - colorMapBounds.left
  let pointY = clickPointY - colorMapBounds.top

  pointX = clamp(pointX, 0, colorMapBounds.width)
  pointY = clamp(pointY, 0, colorMapBounds.height)
  colorSelectorPosition.value = { x: pointX, y: pointY }
  updateColor()
}

function setHue(ev: MouseEvent) {
  const hueMapBounds = hueMap.value.getBoundingClientRect()
  const { clientX } = ev
  let point = clientX - hueMapBounds.left
  point = clamp(point, 0, hueMapBounds.width)
  hueSelectorPosition.value = { x: point, y: 0 }
  updateColor()
}

function setSelectorPosition(color: HashString | null) {
  if (!color) return
  nextTick(() => {
    setColorSelectorPosition(color)
    setHueSelectorPosition(color)
  })
}

const hue = computed(() => {
  if (!hueMap.value) return 0
  const positionX = hueSelectorPosition.value.x || 0
  const width = hueMap.value.getBoundingClientRect().width || 1
  return Math.round((positionX / width) * 360)
})

const updateColor = () => {
  nextTick(() => {
    const colorMapBounds = colorMap.value.getBoundingClientRect()
    const s = Math.round(
      (colorSelectorPosition.value.x / colorMapBounds.width) * 100
    )
    const v =
      100 -
      Math.round((colorSelectorPosition.value.y / colorMapBounds.height) * 100)
    const h = hue.value
    currentColor = HSVToHex(h, s, v)
    emit("update:modelValue", currentColor)
  })
}

watch(sRGBHex, () => {
  if (!isSupported.value || !sRGBHex.value) return
  emit("update:modelValue", sRGBHex.value)
})

watch(
  () => props.modelValue,
  (color) => {
    if (color === currentColor) return
    setSelectorPosition(getRGB(color))
  },
  { immediate: true }
)
</script>
